package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.AuthMapper;
import com.leyou.common.auth.pojo.AppInfo;
import com.leyou.common.auth.pojo.Payload;
import com.leyou.common.auth.pojo.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AuthService {

    @Autowired
    private JwtProperties jwtProp;

    @Autowired
    private UserClient userClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private AuthMapper authMapper;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public void login(String username, String password, HttpServletRequest request, HttpServletResponse response) {
        //校验用户名和密码是否正确
        User user = null;
        try {
            //远程调用user微服务
            user = userClient.findUserByUsernameAndPassword(username, password);
        }catch (Exception e){
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
        //封装User的dto对象
        UserInfo userInfo = new UserInfo(user.getId(), user.getUsername(), "admin");
        //生成token并将token写入浏览器的cookie中
        createTokenToCookie(request, response, userInfo);
    }

    //生成token并将token写入浏览器的cookie中
    private void createTokenToCookie(HttpServletRequest request, HttpServletResponse response, UserInfo userInfo) {
        //生成token
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, jwtProp.getPrivateKey(), jwtProp.getCookie().getExpire());
        //将token写入Cookie
        CookieUtils.newCookieBuilder()
                .request(request)
                .response(response)
                .domain(jwtProp.getCookie().getCookieDomain())
                .httpOnly(true)
                .name(jwtProp.getCookie().getCookieName())
                .value(token)
                .build();//构建cookie并写入浏览器
    }

    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {
        //获取token
        String token = CookieUtils.getCookieValue(request, jwtProp.getCookie().getCookieName());
        //解析token，注意：token不合法或者过期都会抛出异常
        Payload<UserInfo> payload = null;
        try {
            payload = JwtUtils.getInfoFromToken(token, jwtProp.getPublicKey(), UserInfo.class);
        }catch (Exception e){
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        //获取token的id
        String tokenId = payload.getId();
        //判断当前token是否在黑名单
        if(redisTemplate.hasKey(tokenId)){
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        //获取认证token的具体过期时间
        Date expTime = payload.getExpiration();
        //获取用户信息
        UserInfo userInfo = payload.getUserInfo();
        //通过过期时间得到刷新时间
        DateTime refreshTime = new DateTime(expTime).minusMinutes(jwtProp.getCookie().getRefreshTime());
        //判断如果当前时间在刷新时间之后【刷新时间在当前时间之前】，就刷新token，
        if(refreshTime.isBeforeNow()){
            //生成token并将token写入浏览器的cookie中
            createTokenToCookie(request, response, userInfo);
        }
        return userInfo;
    }

    public void logout(HttpServletRequest request, HttpServletResponse response) {
        //获取token
        String token = CookieUtils.getCookieValue(request, jwtProp.getCookie().getCookieName());
        //解析token
        Payload<UserInfo> payload = null;
        try {
            payload = JwtUtils.getInfoFromToken(token, jwtProp.getPublicKey(), UserInfo.class);
        }catch (Exception e){
            //表示token解析失败，本来就没登录，不用退出了。
            return;
        }
        //获取token的id
        String tokenId = payload.getId();
        //获取token的过期时间
        Date extTime = payload.getExpiration();
        //得到过期时间剩余的毫秒数
        long remainSecond = extTime.getTime() - System.currentTimeMillis();
        //如果超过1一秒过期，就将当前token存入redis黑名单中，这样可以适当减轻服务器压力。
        if(remainSecond > 1000){
            redisTemplate.opsForValue().set(tokenId, "随便写", remainSecond, TimeUnit.MILLISECONDS);
        }
        //将浏览器的cookie删除，这步可以省略，其实就是做了一个优化。
        CookieUtils.deleteCookie(jwtProp.getCookie().getCookieName(),
                jwtProp.getCookie().getCookieDomain(), response);
    }

    /**
     * 校验服务的id和名称是否正确
     */
    public Boolean isUsable(Long serverId, String serverName){
        //根据服务id查询服务的对象
        ApplicationInfo applicationInfo = authMapper.selectByPrimaryKey(serverId);
        if(applicationInfo==null){
            log.error("【服务认证失败】服务提供的服务id错误！");
            return false;
        }
        if(!passwordEncoder.matches(serverName, applicationInfo.getSecret())){
            log.error("【服务认证失败】服务提供的服务名称错误！");
            return false;
        }
        return true;
    }

    public String appLogin(Long id, String secret) {
        //校验服务id和服务名称是否正确
        if(!isUsable(id, secret)){
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }
        //获取当前服务可以访问的服务列表
        List<Long> targetIds = authMapper.findApplicationIdsById(id);
        //封装一个app认证时生成jwt所用到的app对象
        AppInfo appInfo = new AppInfo(id, secret, targetIds);
        //生成token
        String token = JwtUtils.generateTokenExpireInMinutes(appInfo, jwtProp.getPrivateKey(), jwtProp.getApp().getExpire());
        return token;
    }
}
